package com.jumi.microservice.common.core.aspect;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Db;
import cn.hutool.json.JSONUtil;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.openservices.ons.api.Message;
import com.aliyun.openservices.ons.api.Producer;
import com.aliyun.openservices.ons.api.SendResult;
import com.jumi.microservice.common.config.rocketmq.configuration.MqConfig;
import com.jumi.microservice.common.constant.CommonLogStateEnum;
import com.jumi.microservice.common.constant.RequestConstant;
import com.jumi.microservice.common.constant.SqlCommandTypeEnum;
import com.jumi.microservice.common.core.aspect.handler.CompareResult;
import com.jumi.microservice.common.core.aspect.handler.DataObjectUtil;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

/**
 * 自定义AOP+注解方式记录controller层日志
 *
 * @author liuz
 */

@Aspect
@Component
public class WebLogAspect {

    private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);

    private static final Map<String, List<DataTem>> TEM_MAP = new ConcurrentHashMap<>();

    private HttpServletRequest request;

    @Autowired
    private DruidDataSource dataSource;

    @Autowired
    MqConfig mqConfig;

    @Autowired
    @Qualifier("log_producer")
    Producer mqProducer;

    @Autowired
    private DataObjectUtil dataObjectUtil;



    /**
     * 声明一个切点,里面是execution表达式
     */
    @Pointcut("@annotation(com.jumi.microservice.common.core.aspect.WebLog)")
    private void controllerAspect() {
    }

    /**
     * <p>
     * 判断线程是否需要记录日志
     * </p>
     */
    public static boolean hasThread(String threadName) {
        return TEM_MAP.containsKey(threadName);
    }

    /**
     * <p>
     * 增加线程数据库操作
     * </p>
     */
    public static void put(String threadName, DataTem dataTem) {
        if (TEM_MAP.containsKey(threadName)) {
            TEM_MAP.get(threadName).add(dataTem);
        }
    }

    /**
     * <p>
     * 切面前执行
     * </p>
     *
     */
    @Before("@annotation(webLog)")
    public void before(WebLog webLog) {
        // 获取线程名，使用线程名作为同一次操作记录
        String threadName = Thread.currentThread().getName();
        TEM_MAP.put(threadName, new LinkedList<>());
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        request = attributes.getRequest();
    }

    /**
     * <p>
     * <p>
     * 切面后执行
     * </p>
     */
    @After("@annotation(webLog)")
    public void after(WebLog webLog) throws SQLException {
        // 获取线程名，使用线程名作为同一次操作记录
        String threadName = Thread.currentThread().getName();
        List<DataTem> list = TEM_MAP.get(threadName);
        if (CollUtil.isEmpty(list)) {
            return;
        }
        List allOldData = new ArrayList<>();
        List allNewData = new ArrayList<>();
        list.stream().forEach(dataTem -> {
            List<?> oldData = dataTem.getOldData();
            if (CollUtil.isEmpty(oldData)) {
                return;
            }
            allOldData.addAll(oldData);
            String ids = oldData.stream().map(o -> {
                try {
                    Method method = o.getClass().getMethod("getId");
                    return method.invoke(o).toString();
                } catch (Exception e) {
                    e.printStackTrace();
                    return null;
                }
            }).filter(ObjectUtil::isNotNull).collect(Collectors.joining(","));
            String sql = dataTem.getSql() + "(" + ids + ")";
            List<?> newData = dataObjectUtil.getOldData(sql, dataTem.getEntityType(), dataSource);
            allNewData.addAll(newData);
            dataTem.setOldData(allOldData);
            dataTem.setNewData(allNewData);
            System.out.println("oldData:" + JSONUtil.toJsonStr(dataTem.getOldData()));
            System.out.println("newData:" + JSONUtil.toJsonStr(dataTem.getNewData()));
        });
        SysLog log = new SysLog();
        log.setAccountId(StringUtils.isNotEmpty(request.getHeader(RequestConstant.ADMIN_ID))?Long.parseLong(request.getHeader(RequestConstant.ADMIN_ID)):0L);
        log.setIp(getClientIp(request));
        //操作菜单名称
        log.setPriorityName(request.getHeader(RequestConstant.PRIORTIRY_NAME));
        log.setOperateTime(LocalDateTime.now());
        log.setOperateAction(webLog.description());
        // 异步对比存库
        this.compareAndSave(list,log);
        // 移除当前线程
        TEM_MAP.remove(threadName);
    }

    /**
     * <p>
     * 对比保存
     * </p>
     *
     * @param list list
     */
    @Async
    public void compareAndSave(List<DataTem> list, SysLog log) throws SQLException {
        //获取所有枚举的属性值
        String o = JSONObject.toJSONString(CommonLogStateEnum.values());
        StringBuilder sb = new StringBuilder();
        //获取操作的id
        StringBuilder ids = new StringBuilder();
        //添加判断是否是update操作 如果是则记录的事修改的主键id
        AtomicBoolean b = new AtomicBoolean(false);
        //判断修改操作是否有过更新
        AtomicBoolean ifup = new AtomicBoolean(true);
        list.stream().forEach(dataTem -> {
            if(SqlCommandTypeEnum.INSERT.getType().equals(dataTem.getType())){
                if(!b.get()) {
                    log.setOperateType(RequestConstant.INSERT_VAL);
                }
                log.setPriorityId(dataTem.getId()!=null?dataTem.getId()+"":null);
            }else if(SqlCommandTypeEnum.UPDATE.getType().equals(dataTem.getType())){
                log.setOperateType(RequestConstant.UPDATE_VAL);
                ifup.set(doUpdate(dataTem, sb, log, ids, o));
                b.set(true);
                log.setUserName("update");
            }else if(SqlCommandTypeEnum.DELETE.getType().equals(dataTem.getType())){
                if(!b.get()) {
                    log.setOperateType(RequestConstant.DELETR_VAL);
                }
            }
        });
        if (sb.length() > 0) {
//            sb.deleteCharAt(0);
            if(RequestConstant.UPDATE_VAL == log.getOperateType()) {
                log.setOperateAction(sb.toString());
            }
        }
        // 存库
        System.err.println(sb.toString());
        if(ifup.get()) {
            if("update".equals(log.getUserName())){
                log.setPriorityId(ids.toString());
            }else {
                if (ids.length() > 0 && StringUtils.isEmpty(log.getPriorityId())) {
                    log.setPriorityId(ids.deleteCharAt(ids.length() - 1).toString());
                }
            }
            saveLog(log);
        }
    }

    private boolean doUpdate(DataTem dataTem, StringBuilder sb, SysLog log, StringBuilder ids, String enums){
        boolean b = false;
        List<?> oldData = dataTem.getOldData();
        List<?> newData = dataTem.getNewData();
        if(CollectionUtil.isEmpty(oldData)||CollectionUtil.isEmpty(newData)){
            return b;
        }
        //获取操作的数据库对象
        String name = null;
        Object obj = oldData.get(0).getClass().getAnnotation(ApiModel.class);
        if(obj!=null){
            name = ((ApiModel) obj).value();
        }
        // 按id排序
        oldData.sort(Comparator.comparingLong(d -> {
            try {
                if(d.getClass()!=null&&d.getClass().getMethod("getId")!=null) {
                    Method method = d.getClass().getMethod("getId");
                    return Long.parseLong(method.invoke(d).toString());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return 0L;
        }));
        newData.sort(Comparator.comparingLong(d -> {
            try {
                if(d.getClass()!=null&&d.getClass().getMethod("getId")!=null) {
                    Method method = d.getClass().getMethod("getId");
                    return Long.parseLong(method.invoke(d).toString());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return 0L;
        }));
        if(StrUtil.isNotEmpty(name)&&!sb.toString().contains(name + " : ")) {
            sb.append(name + " : ");
        }
        final int[] finalI = {0};
        for (int i = 0; i < oldData.size(); i++) {
            List<CompareResult> coms = sameClazzDiff(oldData.get(i), newData.get(i));
            if(coms!=null&&coms.size()>0) {
                b = true;
                coms.stream().forEach(r -> {
                if (finalI[0] == 0 && StrUtil.isEmpty(ids)) {
                    ids.append(r.getId()).append(",");
                }
                if (RequestConstant.IS_DEL.equals(r.getFieldName()) && (RequestConstant.DEL_VAL == (Integer) r.getNewValue())) {
                    //如果是删除操作重新操作log
                    log.setOperateType(RequestConstant.DELETR_VAL);
                }else if(RequestConstant.STATUS_COL.equals(r.getFieldName())){
                    //如果是更新状态转为中文状态
                    sb.append(StrUtil.format("把字段[{}]从[{}]改为[{}]",
                            r.getFieldComment(), (RequestConstant.STATUS_VAL_YES == (Integer) r.getOldValue())?"启用":"禁用",
                            (RequestConstant.STATUS_VAL_YES == (Integer) r.getNewValue())?"启用":"禁用"));
                }else if(enums.contains(r.getFieldName()+r.getOldValue())){
                    //如果是更新状态转为中文状态
                    sb.append(StrUtil.format("把字段[{}]从[{}]改为[{}]",
                            r.getFieldComment(),
                            CommonLogStateEnum.valueOf(r.getFieldName()+r.getOldValue()).getDesc(),
                            CommonLogStateEnum.valueOf(r.getFieldName()+r.getNewValue()).getDesc()));
                } else {
                    String msg = StrUtil.format("把字段[{}]从[{}]改为[{}]", r.getFieldComment(), r.getOldValue(), r.getNewValue());
                    if (!sb.toString().contains(msg)) {
                        sb.append(msg);
                    }
                }
                finalI[0]++;
                });
            }else {
                if(!b) {
                    return b;
                }
            }
        }
        return b;
    }

    private void saveLog(SysLog log) throws SQLException {
        try {
            SendResult result = mqProducer.send(new Message(mqConfig.getLogTopic(), mqConfig.getLogTag(), JSONObject.toJSONString(log).getBytes()));
            logger.info("操作日志-producer-发送成功={}"+result);
        }catch(Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * <p>
     * 相同类对比
     * </p>
     *
     * @param obj1 obj1
     * @param obj2 obj2
     */
    private List<CompareResult> sameClazzDiff(Object obj1, Object obj2) {
        List<CompareResult> results = new ArrayList<>();
        Field[] obj1Fields = obj1.getClass().getDeclaredFields();
        Field[] obj2Fields = obj2.getClass().getDeclaredFields();
        Long id = null;
        for (int i = 0; i < obj1Fields.length; i++) {
            obj1Fields[i].setAccessible(true);
            obj2Fields[i].setAccessible(true);
            Field field = obj1Fields[i];
            try {
                Object value1 = obj1Fields[i].get(obj1);
                Object value2 = obj2Fields[i].get(obj2);
                if ("id".equals(field.getName())) {
                    id = Long.parseLong(value1.toString());
                }
                //如果是更新了时间或用户跳过
                if(RequestConstant.MODIFIER.equals(field.getName())||RequestConstant.UPDATE_TIME.equals(field.getName())){
                    continue;
                }
                if (!ObjectUtil.equal(value1, value2)) {
                    CompareResult r = new CompareResult();
                    r.setId(id);
                    r.setFieldName(field.getName());
                    // 获取注释
                    r.setFieldComment(field.getAnnotation(ApiModelProperty.class).value());
                    r.setOldValue(value1);
                    r.setNewValue(value2);
                    results.add(r);
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return results;
    }

    /**
     * 获取客户端IP
     * @return 客户端IP
     */
    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }


}
